﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Passwords.Models;
using System.Windows.Input;
using System.Windows;
using Passwords.DAL;
using System.Windows.Controls;
using System.Windows.Media;
using System.Security;
using Passwords.Controls;
using System.Diagnostics;
using Passwords.Utils;
using Passwords.Services;
using System.IO;

namespace Passwords.ViewModels
{
    public class AppViewModel : ViewModelBase
    {
        #region ctor

        public AppViewModel()
            : base()
        {
            BindCommands();


            IsServiceEnabled = Passwords.Properties.Settings.Default.IsServiceEnabled;

        }

        #endregion
        #region props&fields

        private string loginErrorText;
        private DataViewModel data;
        private AppDisplayMode appDisplayMode = AppDisplayMode.Login;
        private DataContext context;
        private SyncService syncService;
        private bool isServiceEnabled;
        private bool loginError;
        private BackupViewModel backupModel;
        private bool isServiceFaulted;
        private string serviceError;
        private Backup backup;
        private bool isBusy;
        private double minWidth = 350d;

        /// <summary>
        /// Gets  the minimum witdh for the window, which depends on the AppDisplayMode.
        /// </summary>
        public double MinWidth
        {
            get
            {
                return minWidth;
            }
            private set
            {
                if (minWidth != value)
                {
                    minWidth = value;
                    OnPropertyChanged("MinWidth");
                }
            }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                if (isBusy != value)
                {
                    isBusy = value;
                    OnPropertyChanged("IsBusy");
                }
            }
        }


        public string BackupName
        {
            get { return backup != null && !backup.Restore ? string.Format("Backup: {0} from {1}", backup.Name, backup.DateCreated) : (string)null; }

        }


        /// <summary>
        /// Gets or sets the backup to open when logging in, otherwise null to use the default instance.
        /// </summary>
        public Backup Backup
        {
            get { return backup; }
            set
            {
                if (backup != value)
                {
                    backup = value;
                    OnPropertyChanged("Backup");
                    OnPropertyChanged("BackupName");
                }
            }
        }


        /// <summary>
        /// Gets the plain text of the service error.
        /// </summary>
        public string ServiceError
        {
            get { return serviceError; }
            private set
            {
                if (serviceError != value)
                {
                    serviceError = value;
                    OnPropertyChanged("ServiceError");
                }
            }
        }

        /// <summary>
        /// Gets wether the backup/sync service faulted.
        /// </summary>
        public bool IsServiceFaulted
        {
            get { return isServiceFaulted; }
            private set
            {
                if (isServiceFaulted != value)
                {
                    isServiceFaulted = value;
                    OnPropertyChanged("IsServiceFaulted");
                    ServiceError = value ? syncService.ServiceError : string.Empty;
                }
            }
        }


        public BackupViewModel BackupModel
        {
            get { return backupModel; }
            set
            {
                if (backupModel != value)
                {
                    backupModel = value;
                    OnPropertyChanged("BackupModel");
                }
            }
        }


        /// <summary>
        /// Gets or sets whether the backup/sync service is enabled.
        /// </summary>
        public bool IsServiceEnabled
        {
            get { return isServiceEnabled; }
            set
            {
                if (isServiceEnabled != value)
                {
                    IsServiceFaulted = false;
                    isServiceEnabled = value;
                    OnPropertyChanged("IsServiceEnabled");
                    OnServiceEnabledChanged();
                }
            }
        }


        /// <summary>
        /// Gets or sets whether login faulted.
        /// </summary>
        public bool LoginError
        {
            get { return loginError; }
            set
            {
                if (loginError != value)
                {
                    loginError = value;
                    OnPropertyChanged("LoginError");
                }
            }
        }


        /// <summary>
        /// Gets or sets the plain text why login faulted.
        /// </summary>
        public string LoginErrorText
        {
            get { return loginErrorText; }
            set
            {
                if (loginErrorText != value)
                {
                    loginErrorText = value;
                    LoginError = !string.IsNullOrEmpty(value);
                    OnPropertyChanged("LoginErrorText");
                }
            }
        }


        /// <summary>
        /// Gets the singelton instance of SyncService.
        /// </summary>
        public SyncService SyncService
        {
            get
            {
                if (syncService == null)
                {
                    syncService = new SyncService { DataContext = this.Data };
                    syncService.Faulted += new EventHandler(OnServiceFaulted);
                }
                return syncService;
            }
        }


        protected AppDisplayMode ParentAppDisplayMode { get; private set; }

        /// <summary>
        /// Gets or sets the current application display mode.
        /// </summary>
        public AppDisplayMode AppDisplayMode
        {
            get { return appDisplayMode; }
            set
            {
                if (appDisplayMode != value)
                {
                    var oldValue = appDisplayMode;
                    ParentAppDisplayMode = oldValue;
                    appDisplayMode = value;
                    OnAppDisplayModeChanged(oldValue);
                    OnPropertyChanged("AppDisplayMode");
                }
            }
        }

        /// <summary>
        /// Gets the application settings.
        /// </summary>
        public AppSettings Settings
        {
            get { return App.Settings; }
        }

        /// <summary>
        /// Gets the data viewmodel.
        /// </summary>
        public DataViewModel Data
        {
            get { return data; }
            set
            {
                if (data != value)
                {
                    if (data != null) data.AppDisplayModeChanged -= OnAppDisplayModeChanged;
                    if (value != null) value.AppDisplayModeChanged += OnAppDisplayModeChanged;
                    if (syncService != null) syncService.DataContext = value;
                    data = value;
                    OnPropertyChanged("Data");
                }
            }
        }

        #endregion
        #region CommandBinding

        private void BindCommands()
        {
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.LogoutCommand, LogoutCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.BackCommand, BackCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.LoginCommand, LoginCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.ShowChangePasswordCommand, ShowChangePasswordCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.ChangePasswordCommand, ChangePasswordCommand, CanChangePassword));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.ShowLoginCommand, ShowLoginCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(ConfigCommands.Undo, SettingsCancelCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(ConfigCommands.Save, SettingsSaveCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.ShowSettingsCommand, ShowSettingsCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.ShowBackupsCommand, ShowBackupsCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.RegisterFirewall, RegisterFirewallCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.UnregisterFirewall, UnregisterFirewallCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(Commands.AboutCommand, AboutCommand));

        }

        void CanChangePassword(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = true;
            //e.CanExecute = newPassword == confirmPassword && newPassword != oldPassword && !string.IsNullOrEmpty(newPassword) && newPassword.Length >= 4;
        }

        private AppDisplayMode aboutMode, parentAboutMode;

        private void AboutCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            parentAboutMode = ParentAppDisplayMode;
            aboutMode = AppDisplayMode;
            AppDisplayMode = Models.AppDisplayMode.About;
        }


        /// <summary>
        /// Changes the password in a separate thread to keep the UI responsive:
        /// </summary>
        private void ChangePasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            PasswordBinder binder = e.Parameter as PasswordBinder;

            if (binder != null && binder.IsValid)
            {
                SecureString newPassword = binder.NewPasswordBox.SecurePassword;
                SecureString oldPassword = binder.LoginBox.SecurePassword;
                SecureString confirmPassword = binder.ConfirmPasswordBox.SecurePassword;

                if (DALUtil.IsEqual(newPassword, confirmPassword))
                {
                    IsBusy = true;

                    Func<PasswordError> func = delegate
                    {
                        using (DataContext context = new DataContext())
                        {
                            PasswordError error = context.ChangePassword(oldPassword, newPassword);
                            return error;
                        }
                    };

                    AsyncCallback callback = delegate(IAsyncResult result)
                    {
                        App.Current.Dispatcher.Invoke((Action)delegate
                        {
                            IsBusy = false;
                            PasswordError error = func.EndInvoke(result);
                            if (error == PasswordError.Success)
                            {
                                AppDisplayMode = Models.AppDisplayMode.Login;
                            }
                            else
                            {
                                LoginErrorText = error.AsString();
                            }
                        });
                    };

                    func.BeginInvoke(callback, null);
                }
                else
                {
                    LoginErrorText = PasswordError.NoMatch.AsString();
                }

                //newPassword.Clear();
                //oldPassword.Clear();
                //confirmPassword.Clear();
            }
            GC.Collect();
        }

        private void SettingsCancelCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            Settings.Undo();
            AppDisplayMode = ParentAppDisplayMode;
        }

        private void SettingsSaveCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            Settings.SaveSettings(Settings.Window);
            AppDisplayMode = ParentAppDisplayMode;
        }

        private void ShowLoginCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            LoginErrorText = string.Empty;
            LoginError = false;
            AppDisplayMode = Models.AppDisplayMode.Login;

        }

        private void ShowSettingsCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            AppDisplayMode = Models.AppDisplayMode.Settings;
        }

        private void ShowBackupsCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            AppDisplayMode = Models.AppDisplayMode.BackupAndRestore;
        }

        private void UnregisterFirewallCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            ServiceUtil.UnregisterFirewall();
        }

        private void RegisterFirewallCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            ServiceUtil.RegisterFirewall();
        }



        private void ShowChangePasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            LoginErrorText = string.Empty;
            LoginError = false;
            AppDisplayMode = Models.AppDisplayMode.ChangePassword;

        }

        private void LoginCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;


            PasswordBox box = e.Parameter as PasswordBox;
            SecureString password = box.SecurePassword;
            if (context == null && password != null)
            {
                IsBusy = true;
                Func<SecureString, bool> action = delegate(SecureString p)
                {
                    return Login(p);
                };

                AsyncCallback callback = delegate(IAsyncResult result)
                {
                    bool success = action.EndInvoke(result);
                    App.Current.Dispatcher.Invoke((Action)delegate
                    {
                        IsBusy = false;
                        if (success)
                        {
                            Data = new DataViewModel(context);
                            this.AppDisplayMode = AppDisplayMode.Passwords;
                        }
                        else
                        {
                            LoginError = true;
                            context.Dispose();
                            context = null;
                        }
                    });
                };

                action.BeginInvoke(password, callback, null);

            }
            if (box != null) box.Clear();
        }

        private bool Login(SecureString password)
        {
            context = backup == null || backup.Restore ? new DataContext() : new DataContext("open");

            if (backup != null)
            {
                context.WritePasswordHash(backup.PasswordHash);
            }
            else
            {
                DbInitializerUtil.InitializeDb();
            }
            bool result = context.Login(password);
            if (result && backup != null)
            {
                context.RestoreBackup(backup);
            }

            return result;
        }

        private void BackCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            ParentAppDisplayMode = parentAboutMode;
            this.AppDisplayMode = aboutMode;
        }

        private void LogoutCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            this.AppDisplayMode = AppDisplayMode.Login;
            Logout();
            Backup = null;
        }

        #endregion
        #region methods

        private void Logout()
        {
            if (Data != null)
            {
                Data.Dispose();
                Data = null;
            }
            if (context != null)
            {
                context.Dispose();
                context = null;
            }
        }


        private void OnAppDisplayModeChanged(object sender, EventArgs e)
        {
            AppDisplayMode = data.AppDisplayMode;
        }

        protected void OnAppDisplayModeChanged(AppDisplayMode oldValue)
        {
            if (data != null) data.AppDisplayMode = appDisplayMode;

            switch (oldValue)
            {
                case Models.AppDisplayMode.BackupAndRestore:
                    if (BackupModel != null) BackupModel.Dispose();
                    BackupModel = null;
                    break;
            }

            switch (appDisplayMode)
            {
                case Models.AppDisplayMode.BackupAndRestore:
                    BackupModel = new BackupViewModel(this);
                    break;

                case Models.AppDisplayMode.ChangePassword:
                    LoginError = false;
                    LoginErrorText = string.Empty;
                    break;

                case Models.AppDisplayMode.Login:
                    LoginError = false;
                    LoginErrorText = string.Empty;
                    Logout();
                    break;
            }
            MinWidth = GetMinWidthFromAppDisplayMode(appDisplayMode);
        }

        private static double GetMinWidthFromAppDisplayMode(Models.AppDisplayMode appDisplayMode)
        {
            switch (appDisplayMode)
            {
                case AppDisplayMode.Categories:
                    return 650;

                case AppDisplayMode.Password:
                case AppDisplayMode.Passwords:
                case AppDisplayMode.Login:
                    return 350d;

                default: return 520d;
            }
        }

        private void OnServiceEnabledChanged()
        {
            if (isServiceEnabled) SyncService.Open();
            else if (syncService != null) syncService.Close();
        }

        void OnServiceFaulted(object sender, EventArgs e)
        {
            string error = syncService.ServiceError;
            App.Current.Dispatcher.BeginInvoke((Action)delegate
            {
                IsServiceEnabled = SyncService.IsOpened;
                IsServiceFaulted = true;
                ServiceError = error;
            });
        }


        #endregion
    }
}
